#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include "MQTTClient.h"
#include <unistd.h>

//需自己更改
#define ADDRESS     "tcp://16.110.110.110:1883"  // 你的MQTT服务器地址
#define CLIENTID    "file_client"  // 客户端ID

#define PUBTOPIC       "device/ota/file/"  // 发布文件内容（分片发送）主题
#define SUBTOPIC       "device/ota/request/#"  // 接收OTA更新请求主题

#define QOS         0           // 服务质量
#define TIMEOUT     10000       // 超时设置
#define CHUNK_SIZE  256         // 每片的大小
#define FILE_PATH   "/root/snap/lxd/cs/mqtt/11.bin"
#define CRC_POLY    0x1021      // CRC 多项式
#define device_MAX  50          // 设备编号最大值

#define OTASTART        0x0001
#define OTACHUNKOK      0x0002
#define OTACHUNKOFF     0x0004
#define OTAOK           0x0008
#define OTAERROR        0x0010

typedef unsigned          char uint8_t;
typedef unsigned short     int uint16_t;
typedef unsigned           int uint32_t;

uint16_t OTA_flag = 0;
MQTTClient client;
const char *file_path = FILE_PATH;

// 用于存储设备编号的结构体
typedef struct {
    char device_id[64];
} ota_request_t;

// 用于存储设备编号的文件传输状态
uint8_t ota_device_status[device_MAX] = {0};

// 连接MQTT服务器
int connect_mqtt(MQTTClient *client) {
    MQTTClient_connectOptions conn_opts = MQTTClient_connectOptions_initializer;
    int rc;

    // 设置连接选项
    conn_opts.keepAliveInterval = 20;
    conn_opts.cleansession = 1;

    rc = MQTTClient_connect(*client, &conn_opts);
    if (rc != MQTTCLIENT_SUCCESS) {
        printf("Failed to connect to MQTT server, return code %d\n", rc);
        return rc;
    }
    printf("Connected to MQTT server\n");
    return MQTTCLIENT_SUCCESS;
}

//延时XMS函数
void msleep(int milliseconds) {
    usleep(milliseconds * 1000); // 将毫秒转换为微秒
}

// CRC 校验函数
uint16_t calculate_crc(uint8_t *data, size_t length) {
    uint16_t crc = 0xFFFF;
    for (size_t i = 0; i < length; i++) {
        crc ^= (uint16_t)data[i] << 8;
        for (uint8_t j = 0; j < 8; j++) {
            crc = (crc & 0x8000) ? (crc << 1) ^ CRC_POLY : (crc << 1);
        }
    }
    return crc;
}

// 发布MSG消息
int publish_MSG(MQTTClient *client, char *data, int len, const char *topic) {

    MQTTClient_message pubmsg = MQTTClient_message_initializer;
    pubmsg.payload = data;
    pubmsg.payloadlen = len;
    pubmsg.qos = 0;
    pubmsg.retained = 0;

    int rc = MQTTClient_publishMessage(*client, topic, &pubmsg, NULL);
    if (rc != MQTTCLIENT_SUCCESS) {
        printf("Failed to publish error");
        return rc;
    }
    return 0;
}

// 发布文件内容（分片发送）
int publish_file(MQTTClient *client, const char *file_path, char *topic ,uint8_t ota_device_number) {
    uint16_t File_Crc;
    FILE *file = fopen(file_path, "rb");
    if (file == NULL) {
        OTA_flag = 0;
        printf("Failed to open file: %s\n", file_path);
        return -1;
    }

    // 获取文件大小
    fseek(file, 0, SEEK_END);
    long file_size = ftell(file);
    fseek(file, 0, SEEK_SET);

    // 计算总分片数
    int total_chunks = (file_size + CHUNK_SIZE - 1) / CHUNK_SIZE;

    // 分片发送
    for (int chunk_index = 0; chunk_index < total_chunks; ++chunk_index) {
        // 每片数据的大小
        int chunk_len = (chunk_index == total_chunks - 1) ? (file_size % CHUNK_SIZE) : CHUNK_SIZE;

        // 读取文件分片
        char *chunk = (char *)malloc(CHUNK_SIZE);
        if (chunk == NULL) {
            OTA_flag |= OTAERROR;
            printf("Failed to allocate memory for chunk\n");
            fclose(file);
            return -1;
        }
        memset(chunk,0,256);
        fread(chunk, 1, chunk_len, file);

        //计数文件数据CRC
        File_Crc = calculate_crc(chunk,256);

        // 构造消息
        char msg[CHUNK_SIZE + 64];  // 包括分片编号和总分片数的额外空间
        snprintf(msg, sizeof(msg), "Crc :%05d,Chunk %03d/%03d: ",File_Crc, chunk_index + 1, total_chunks);
        int len;
        len = strlen(msg);
        memcpy(msg + len, chunk, 256);

        //发布分片
        MQTTClient_message pubmsg = MQTTClient_message_initializer;
        pubmsg.payload = msg;
        pubmsg.payloadlen = len + 256;
        pubmsg.qos = QOS;
        pubmsg.retained = 0;

        int rc = MQTTClient_publishMessage(*client, topic, &pubmsg, NULL);
        if (rc != MQTTCLIENT_SUCCESS) {
            printf("Failed to publish chunk %d, return code %d\n", chunk_index + 1, rc);
            free(chunk);
            fclose(file);
            OTA_flag |= OTAERROR;
            return rc;
        }

        printf("Published chunk %d/%d\n", chunk_index + 1, total_chunks);

        //等待答复与重传实现
        uint16_t i = 0;
        while (1){
            i++;
            msleep(1);
            if (ota_device_status[ota_device_number] & OTACHUNKOK){
                ota_device_status[ota_device_number] &= ~OTACHUNKOK;
                break;
            }else if (ota_device_status[ota_device_number] & OTACHUNKOFF){
                ota_device_status[ota_device_number] &= ~OTACHUNKOFF;
                chunk_index--;
                break;
            }
            //长时间没有应答
            if(i>=TIMEOUT){   
                free(chunk);
                fclose(file);
                ota_device_status[ota_device_number] |= OTAERROR;
                printf("File published error\n");
                return -1;
            }
        }
        
        free(chunk);
    }

    ota_device_status[ota_device_number] |= OTAOK;
    fclose(file);
    printf("File published successfully\n");
    return MQTTCLIENT_SUCCESS;
}

// OTA 处理线程
void *ota_thread(void *param) {
    ota_request_t *request = (ota_request_t *)param;
    char topic[128];

    // 格式化目标响应主题
    snprintf(topic, sizeof(topic), "%s%s", PUBTOPIC, request->device_id);

    uint8_t number = atoi(request->device_id);

    // 模拟向设备发送 OTA 响应
    printf("Sending OTA update to device %s...\n", request->device_id);
    // 在这里可以根据实际情况发送 OTA 更新消息
    // 例如，使用 MQTT 发布一条消息：
    publish_file(&client,file_path,topic,number);

    // 清理资源
    free(request);
    return NULL;
}

// 获取客户端编号
void get_Client_num(const char *topic, char *id){
    const char *device_id_start = strrchr(topic, '/');
    if (device_id_start) {
        snprintf(id, sizeof(device_id_start), "%s", device_id_start + 1);
    } else {
        printf("Error: Unable to extract device_id from topic.\n");
        return;
    }
}

// 处理 OTAGet 请求的函数
void ota_get_handler(const char *topic) {
    ota_request_t *request = (ota_request_t *)malloc(sizeof(ota_request_t));

    // 解析设备编号，从主题中提取设备编号
    // 主题格式为 "device/ota/request/{device_id}"，提取 {device_id}
    // 例如， "device/ota/request/12345"
    const char *device_id_start = strrchr(topic, '/');
    if (device_id_start) {
        snprintf(request->device_id, sizeof(request->device_id), "%s", device_id_start + 1);
    } else {
        printf("Error: Unable to extract device_id from topic.\n");
        free(request);
        return;
    }

    // 创建新线程处理该请求
    pthread_t thread;
    pthread_create(&thread, NULL, ota_thread, (void *)request);
    pthread_detach(thread);  // 分离线程，使其在完成后自动回收
}

// MQTT消息到达时的回调函数
int messageArrived(void *context, char *topicName, int topicLen, MQTTClient_message *message) {
    printf("Message arrived on topic: %s\n", topicName);
    printf("Message: %.*s\n", message->payloadlen, (char*)message->payload);

    if (!strncmp(topicName, "device/ota/request", strlen("device/ota/request")) && !strncmp((char*)message->payload, "OTAGet", 6)) {
            char id[10] = {0};
            char topic[128];

            get_Client_num(topicName,id);
            // 格式化目标响应主题
            snprintf(topic, sizeof(topic), "%s%s", PUBTOPIC, id);
            printf("\r\n%s\r\n",topic);
            publish_MSG(&client,"OTAOK",5,topic);
            msleep(500);
            ota_get_handler(topic);
            OTA_flag |= OTASTART;
        }
    else if (!strncmp(topicName, "device/ota/request", strlen("device/ota/request")) && !strncmp((char*)message->payload, "error", strlen("error"))) {
            OTA_flag = 2;
            msleep(500);
        }
    else if (!strncmp(topicName, "device/ota/request", strlen("device/ota/request")) && !strncmp((char*)message->payload, "ChunkOK", strlen("ChunkOK"))) {
            // OTA_flag |= OTACHUNKOK;
            char id[3] = {0};
            get_Client_num(topicName,id);
            uint8_t number = atoi(id);
            ota_device_status[number] |= OTACHUNKOK;
            msleep(500);
        }
    else if (!strncmp(topicName, "device/ota/request", strlen("device/ota/request")) && !strncmp((char*)message->payload, "ChunkOff", strlen("ChunkOff"))) {
            // OTA_flag |= OTACHUNKOFF;
            char id[3] = {0};
            get_Client_num(topicName,id);
            uint8_t number = atoi(id);
            ota_device_status[number] |= OTACHUNKOFF;
            msleep(500);
        }


    MQTTClient_freeMessage(&message);
    MQTTClient_free(topicName);
    return 1;
}

int main() {
    int rc,time_cnt=0;
    int delay = 1;  // 初始延迟为1秒
    

    // 初始化MQTT客户端
    MQTTClient_create(&client, ADDRESS, CLIENTID, MQTTCLIENT_PERSISTENCE_NONE, NULL);

    // 设置回调函数
    MQTTClient_setCallbacks(client, NULL, NULL, messageArrived, NULL);

    // 连接到MQTT服务器
    // rc = connect_mqtt(&client);
    // if (rc != MQTTCLIENT_SUCCESS) {
    //     return rc;
    // }

    while ((rc = connect_mqtt(&client)) != MQTTCLIENT_SUCCESS) {
        printf("Failed to connect to MQTT server, return code %d. Retrying in %d seconds...\n", rc, delay);
        msleep(delay * 1000);  // 延时
        delay = delay < 32 ? delay * 2 : 32;  // 最大延时为32秒
        time_cnt++;
        if (time_cnt >= 10) {
            printf("Failed to connect after 10 attempts\n");
            return rc;
        }
    }

    // 订阅 OTA 请求主题（使用通配符 #）
    rc = MQTTClient_subscribe(client, SUBTOPIC, 0);
    if (rc != MQTTCLIENT_SUCCESS) {
        printf("Failed to subscribe to topic %s, return code %d\n", SUBTOPIC, rc);
        return rc;
    }

    while (1)
    {
        uint8_t i;
        char str[4] = {0};
        char topic[128];
            
        for (size_t i = 0; i < device_MAX; i++)
        {
            if(ota_device_status[i] & OTAOK){
                snprintf(str, sizeof(str), "%lu",(size_t)i);
                snprintf(topic, sizeof(topic), "%s%s", PUBTOPIC, str);
                ota_device_status[i] &= ~OTAOK;
                publish_MSG(&client,"OTAOVER",7,topic);
                memset(str,0,4);
            }else if(ota_device_status[i] & OTAERROR){
                ota_device_status[i] &= ~OTAERROR;
                snprintf(str, sizeof(str), "%lu",(size_t)i);
                snprintf(topic, sizeof(topic), "%s%s", PUBTOPIC, str);
                publish_MSG(&client,"OTAERROR",8,topic);
                memset(str,0,4);
            }
        }
        
        /* code */
    }
    
    MQTTClient_disconnect(client, 10000);
    MQTTClient_destroy(&client);

    return rc;
}
